<?php
include_once 'Node.php';
include_once 'TokenParser.php';
include_once 'Char.php';

class Parser{
	
	var $match = array('"'=>'"', "'"=>"'", '('=>')', '['=>']', '<'=>'>', '{'=>'}');

	public function parse($source){
		$root = new Node();
		$char = new Char();
		$tokenParsers = $this->initParsers();

		$token = $this->resetToken();
		$len = strlen($source);
		
		$parent = array(&$root);
		$current = new Node();
		
		$inBracket = false;

		for($i =0; $i<$len; ++$i){
			$c = $source[$i];
			// handle text first
			if(in_array($c, array('"', "'", '['))){				
				$ret = $this->findCDataEnd($source, $this->match[$c], $i+1);
				$i = $ret[1];
				if(!$inBracket && $c == '['){
					$this->proceedToken($token, $current, $tokenParsers);
					$this->resetToken($token);
					$current->setAttribute('[]', $ret[0]);
				}else{
					$token[$token[0]] .= $ret[0];					
				}				
			}
			
			// ',' ')' and '=' for attributes setting in '()'
			elseif($c == ',' || $c ==')'){
				if($token[$token[0]] != ''){				
					if($token[0] == 1){
						$current->setAtAttribute($token[1]);
						$this->resetToken($token);
					}else if($token[0] == 2){
						$current->setAttribute($token[1], $token[2]);
						$this->resetToken($token);
					}else{
						// should be error...
					}
				}
				if($c == ')'){
					$inBracket = false;
				}
			}else if($c == '='){
				$token[0] = 2;
			}
			
			// handle '()' and '{}' bracket
			else if(in_array($c, array('(', '{', '}'))){
				$this->proceedToken($token, $current, $tokenParsers);
				$this->resetToken($token);

				if($c == '{'){
					array_push($parent, $current);
					$current = new Node();
					
				}elseif($c == '}'){
					$p = array_pop($parent);
					if(!$current->isEmpty()){
						$p->addChild($current);
					}
					if(!$p->isEmpty()){
						$pp = end($parent);
						$pp->addChild($p);
					}
					$current = new Node();
				}elseif($c == '('){
					$inBracket = true;
				}
			}
			
			// handle tokens
			elseif($char->isSpecialChar($c)){
				$this->proceedToken($token, $current, $tokenParsers);
				$this->resetToken($token);
				$token[$token[0]] .= $c;
			}elseif($char->isTokenChar($c)){
				$token[$token[0]] .= $c;
			}
			else{				
				$this->proceedToken($token, $current, $tokenParsers);
				$this->resetToken($token);
				if(!$current->isEmpty() && !$inBracket){
					$p = end($parent);
					$p->addChild($current);
					
					$current = new Node();
				}				
			}
		}
		
			$this->proceedToken($token, $current, $tokenParsers);
		if(!$current->isEmpty() && !$inBracket){
			$p = end($parent);
			$p->addChild($current);
		}
		return $root;
	}

	function initParsers(){
		return array(new TokenParser());
	}
	
	function resetToken(&$token = NULL){
		$token = array(1,'','');
		return $token;
	}

	function findCDataEnd(&$content, &$endChar, $offset){
		$dollarEnd = false;
		$idx = $offset;
		if($content[$idx++] == '^'){
			$dollarEnd = true;
		}

		$len = strlen($content);
		if($dollarEnd){
			while($idx < $len && $content[$idx] != '$' || $content[$idx+1] != $endChar){
				$idx++;
			}
			return array(substr($content, $offset+1, $idx-$offset-1), $idx+1);
		}else{
			while($idx < $len && $content[$idx++] != $endChar);
			return array(substr($content, $offset, $idx-$offset-1), $idx-1);
		}
		
		// should be error...
		return array('', $offset);
	}

	function trimText(&$text){
		$text = trim($text);

		if($text[0] == '"' || $text[0] == "'" || $text[0] == '['){
			if($text[1] == '^'){
				$text = substr($text, 2);
			}else{
				$text = substr($text, 1);
			}
		}

		$len = strlen($text);
		if($text[$len-1] == '"' || $text[$len-1] == "'"
		|| $text[$len-1] == ']'){
			if($text[$len-2] == '$'){
				$text = substr($text, 0, $len-2);
			}else{
				$text = substr($text, 0, $len-1);
			}
		}else
		return $text;
	}

	function proceedToken(&$token, Node &$current, &$tokenParsers){
		$token = trim($token[1]);
		$ret = false;
		if($token != ''){
			foreach($tokenParsers as $parser){
				if($parser->parse($token, $current)){
					$ret = true;
					break;
				}
			}
		}
		$this->resetToken($token);
		return $ret;
	}

}